import re

class Variables: # A container for variables that need to be passed between functions. 
    MeshArray = list()
    VertArray = list()
    UVArray = list()
    UVPadding = 0
    FaceVerts = 0
    FaceBytes = 0
    FacePadding = 0
    FaceCount = 0
    VertCount = 0
    Verts = 0
    HeaderPadding = 0





def Header_Parser():
    file.seek(-7,1) # Jump back to important header bytes.
    byte = file.read(1) # This byte identifies the mesh type.

    if byte == b'\x12': # 'Loose' byte triangles. 
        vars.FaceVerts = 3
        vars.FaceBytes = 1
        vars.FacePadding = 3
        vars.HeaderPadding = 6
        vars.UVPadding = 6
       
    elif byte == b'\x14': # Byte faces.
        vars.FaceVerts = 4
        vars.FaceBytes = 1
        vars.FacePadding = 4
        vars.HeaderPadding = 8
        vars.UVPadding = 8
        
    elif byte == b'\x11': # 'Loose' short triangles.
        vars.FaceVerts = 3
        vars.FaceBytes = 2
        vars.FacePadding = 6
        vars.HeaderPadding = 8
        vars.UVPadding = 8        

    elif byte == b'\x13': # Short faces.
        vars.FaceVerts = 4
        vars.FaceBytes = 2
        vars.FacePadding = 8
        vars.HeaderPadding = 8
        vars.UVPadding = 8        

    vars.FaceCount = int.from_bytes(file.read(2), 'little') # Get number of faces following header.
    file.seek(4 + vars.HeaderPadding,1) # Skip rest of header plus padding.





def Face_Parser():
    CurrentMesh = len(vars.MeshArray) - 1 # Get the newest entry in the mesh array. 
    vars.MeshArray[CurrentMesh].append([0,0,0]) if vars.FaceVerts == 3 else vars.MeshArray[CurrentMesh].append([0,0,0,0]) # Create a new entry within it.
    CurrentFaceIndex = len(vars.MeshArray[CurrentMesh]) - 1 # Get this new entry.
    
    for x in range(vars.FaceVerts):    
        vars.MeshArray[CurrentMesh][CurrentFaceIndex][x] = int.from_bytes(file.read(vars.FaceBytes), 'little') # Write verts to the current face index.   

        if vars.MeshArray[CurrentMesh][CurrentFaceIndex][x] >= vars.VertCount: vars.VertCount = vars.MeshArray[CurrentMesh][CurrentFaceIndex][x] + 1 # Get total number of verts from largest face vertex. Must add one for accurate count.   
        
    file.seek(vars.FacePadding, 1) # Skip padding between the face and its UVs.





def UV_Parser():
    CurrentMesh = len(vars.UVArray) - 1 # Get the newest entry in the UV array.

    for x in range(vars.FaceVerts): # Every vert has a UV.
        vars.UVArray[CurrentMesh].append([0,0]) # Create a new entry within it.
        CurrentUVIndex = len(vars.UVArray[CurrentMesh]) - 1 # Get this new entry.

        # Get and store the UV coordinates.
        vars.UVArray[CurrentMesh][CurrentUVIndex][0] = int.from_bytes(file.read(2), "little", signed = "True") / 4096
        vars.UVArray[CurrentMesh][CurrentUVIndex][1] = 1 - (int.from_bytes(file.read(2), "little", signed = "True") / 4096)





def Vert_Parser():
    FloatStart = file.tell() - 16 # Get back to the start of the vert block.  
    FloatCount = 0
    CoordPadding = CoordBytes =  2
    line = file.read(16)
    
    while re.search(b'....\x00\x00\x80\?', line) != None: # Skip floats that precede signed shorts.
        FloatCount += 1

        if FloatCount > 50: # Certain models uses signed floats instead of signed shorts, a large number of floats is a clear indication.
            file.seek(FloatStart, 0)
            CoordBytes = CoordPadding = 4            
            break

        line = file.read(16) 

    if FloatCount < 50: file.seek(-16, 1) # Jump back to the start of the first signed short.
        
    for x in range(vars.VertCount): # Write the X, Y, Z coordinates to the vert array, adjust them for proper scale.
        vars.VertArray.append([0, 0, 0])

        for y in range(3): vars.VertArray[x][y] = (int.from_bytes(file.read(CoordBytes), "little", signed = "True") / 256) * 0.313
        file.seek(CoordPadding, 1) # Skip padding between eacg coordinate.





def Face_Writer():
    FaceVerts = len(vars.MeshArray[0][0]) # Determine if the mesh uses tris or faces.

    for x in range(len(vars.MeshArray[0])): # For entries in the current mesh.

        obj.write('f ')
        
        for y in range(3): # Incrementally write three verts and UVs.
            vars.Verts += 1
            obj.write(str(vars.Verts) + '/' + str(vars.Verts) + ' ')

        obj.write('\n')

        if FaceVerts == 4:
            obj.write('f ')

            vars.Verts += 1

            for z in range(3): # Decrement to properly form the other face.
                obj.write(str(vars.Verts) + '/' + str(vars.Verts) + ' ')
                vars.Verts -= 1

            
            # Increment to begin the next face.
            vars.Verts += 3
            obj.write('\n')   





def UV_Writer():
    CurrentMeshLength = len(vars.UVArray[0]) # Get thhe number of UVs for the current mesh.
    
    for x in range(CurrentMeshLength): 
        obj.write("vt ")

        for y in range(2):
            obj.write(str(vars.UVArray[0][x][y]) + ' ')

        obj.write('\n')   





def Vert_Writer():
    CurrentMeshLength = len(vars.MeshArray[0]) # Get the length of the current mesh.
    FaceVertCount = len(vars.MeshArray[0][0]) # Determine if the mesh uses tris or faces.

    for x in range(CurrentMeshLength): 

        for y in range(FaceVertCount):
            obj.write("v ")

            for z in range (3):
                obj.write(str(vars.VertArray[vars.MeshArray[0][x][y]][z]) + " ") # Write the individual coordinates

            obj.write("\n")





components = 1
vars = Variables()

for i in range(components): # Certain model types (legs, tanks, etc) store parts of the model sequentially, rather than in one large block.
 
    with open('', 'rb') as file:

        bytes = file.read(8)

        while re.fullmatch(b'[\x01-\xff][\x01-\xff][\x01-\xff][\x01-\xff]\x00\x00\x80\?', bytes) == None: # Search for headers until vert block is reached. 

            if re.fullmatch(b'[\x00-\x09]\x12[\x01-\xff][\x00-\xff][\x00-\x09]\x00[\x00-\x09]\x00', bytes) != None or re.fullmatch(b'[\x00-\x09]\x14[\x01-\xff][\x00-\xff][\x00-\x09]\x00[\x00-\x09]\x00', bytes) != None or re.fullmatch(b'[\x00-\x09]\x11[\x01-\xff][\x00-\xff][\x00-\x09]\x00[\x00-\x09]\x00', bytes) != None or re.fullmatch(b'[\x00-\x09]\x13[\x01-\xff][\x00-\xff][\x00-\x09]\x00[\x00-\x09]\x00', bytes) != None: #Mesh header formats.

                #Create a new entry in the mesh and UV array for the new object.    
                vars.MeshArray.append([])  
                vars.UVArray.append([])

                Header_Parser()

                for x in range(vars.FaceCount):
                    Face_Parser()
                    UV_Parser()

                    if x != vars.FaceCount - 1: file.seek(vars.UVPadding, 1) #UVs padded until the last set.  

            file.seek(-7,1) # Progress by one to ensure nothing is missed.
            bytes = file.read(8)

        Vert_Parser()
        
    with open('', 'w') as obj: # With all the data parsed, move on to writing it in the proper order. 

        MeshCount = 1

        for x in range(len(vars.MeshArray)):

            obj.write('object Mesh ' + str(MeshCount) + '\n') # Separate the meshes.

            Vert_Writer()
            UV_Writer()
            Face_Writer()
            
            # Remove the heads of these arrays as they're no longer needed. 
            vars.MeshArray.pop(0)
            vars.UVArray.pop(0)

            MeshCount = MeshCount + 1

    vars.VertArray.clear() # Clear the vert array once all data written. 
